iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0

Day 17 的 Sentinel 解決了「主節點掛了有人接手」,但能寫的還是只有一台主節點,資料也要一台裝得下。

Cluster(叢集)的做法是開好幾台主節點,把資料拆開放,每台只管一部分。

常用指令

  • redis-cli --cluster create IP:port ...(把好幾台 Redis 組成一個 Cluster)
  • CLUSTER KEYSLOT key(看這個 key 會被分到哪個 slot)
  • redis-cli -c(碰到 MOVED 自動跳到對的那台)

開起來

先把 Day 17 的環境關掉(在主機的終端機下):

docker compose -f docker-compose-sentinel.yml down

一樣在 Day 2 的 docker-compose.yml 旁邊,另外寫一份 docker-compose-cluster.yml,六台 Redis:

# Day 18 Cluster:三主三從,六台都開 cluster 模式
name: day18

services:
  net: # 只負責開網路跟 port,其他台都借它的網路
    image: redis:7-alpine
    container_name: day18-net
    command: sleep infinity
    init: true # 讓 docker stop 不用等 10 秒
    ports:
      - "7001-7006:7001-7006" # 六台 Redis

  redis-7001:
    image: redis:7-alpine
    container_name: redis-7001
    network_mode: service:net
    command: redis-server --port 7001 --cluster-enabled yes # 開 cluster 模式

  redis-7002:
    image: redis:7-alpine
    container_name: redis-7002
    network_mode: service:net
    command: redis-server --port 7002 --cluster-enabled yes

  redis-7003:
    image: redis:7-alpine
    container_name: redis-7003
    network_mode: service:net
    command: redis-server --port 7003 --cluster-enabled yes

  redis-7004:
    image: redis:7-alpine
    container_name: redis-7004
    network_mode: service:net
    command: redis-server --port 7004 --cluster-enabled yes

  redis-7005:
    image: redis:7-alpine
    container_name: redis-7005
    network_mode: service:net
    command: redis-server --port 7005 --cluster-enabled yes

  redis-7006:
    image: redis:7-alpine
    container_name: redis-7006
    network_mode: service:net
    command: redis-server --port 7006 --cluster-enabled yes
  • --cluster-enabled yes:開 cluster 模式,沒加的話就是一般的單機 Redis
  • network_mode: service:net:大家都用 127.0.0.1 + 不同 port 找對方,主機的 Java 也連得到

光開起來還不是 Cluster,要再用 --cluster create 把六台組起來(在主機的終端機下):

docker compose -f docker-compose-cluster.yml up -d

docker exec redis-7001 redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1 --cluster-yes
  • --cluster-replicas 1:每個主節點配一台從節點,六台就是三主三從
  • --cluster-yes:不用再問一次,直接照它分好的來

https://ithelp.ithome.com.tw/upload/images/20260930/20184209yGpWokK9R0.png

  • M: 是主節點,7001、7002、7003 各拿一段 slot:0-5460、5461-10922、10923-16383
  • S: 是從節點,replicates 後面那串是它跟的主節點 ID,對一下上面的 M:,7004 跟 7001、7005 跟 7002、7006 跟 7003
  • [WARNING] 是提醒從節點跟主節點在同一台機器上,正式環境要放在不同機器上

主節點掛了,它的從節點會自己升上去,Cluster 自己就會切換,不用另外開 Sentinel。

16384 個 slot

Cluster 準備了 16384 個格子,叫做 slot(槽),編號 0 到 16383。三台主節點各管一段,剛剛截圖的 0-5460 就是 7001 管的那段。

存一個 key 的時候,Redis 會用 key 的名字算出一個編號,編號落在誰那段,key 就存到誰那台。

進 7001 試試看(在主機的終端機下):

docker exec -it redis-7001 redis-cli -p 7001

CLUSTER KEYSLOT user:1

SET user:1 gary

https://ithelp.ithome.com.tw/upload/images/20260930/201842096apUQImb6K.png

user:1 算出來是 10778,在 5461-10922 那段,歸 7002 管。7001 不會幫忙轉過去,只回一個 MOVED,告訴你該去找 7002。

自動跳過去

exit 出來,加上 -c 再進去一次(在主機的終端機下):

docker exec -it redis-7001 redis-cli -c -p 7001

SET user:1 gary

GET user:1

https://ithelp.ithome.com.tw/upload/images/20260930/20184209UfDoCDiMl6.png

redis-cli 收到 MOVED 自己跳到 7002 寫進去,前面的提示也從 7001 變成 7002。

CROSSSLOT

一樣在 -c 裡面,一次寫兩個 key:

CLUSTER KEYSLOT a

CLUSTER KEYSLOT b

MSET a 1 b 2

CLUSTER KEYSLOT f

MSET b 1 f 2

https://ithelp.ithome.com.tw/upload/images/20260930/20184209uINmRZqT3g.png

a 在 15495(7003),b 在 3300(7001),slot 不一樣,-c 也沒辦法,直接噴 CROSSSLOT。

f 在 3168,跟 b 一樣歸 7001 管,但 MSET b 1 f 2 還是噴錯。一個指令裡的 key,slot 編號要一模一樣,在同一台也不行。除了 MSET,一次 DEL 好幾個 key、Day 7 的 SINTER 也會遇到。

hash tag

要一起動的 key,可以在名字裡加大括號,Redis 只拿大括號裡面的字來算 slot:

CLUSTER KEYSLOT {user:1}:cart

CLUSTER KEYSLOT {user:1}:view

MSET {user:1}:cart 1 {user:1}:view 2

https://ithelp.ithome.com.tw/upload/images/20260930/20184209yOESITU1dB.png

兩個都只算 user:1,所以都是 10778,同一個 slot,MSET 就過了。

代價是同一個大括號的 key 全部擠在同一台,六台的 Cluster 就只剩一台在做事。

Java 改連 Cluster

exit 出來,在 application.yml 旁邊新增一份 application-cluster.yml:

# Day 18:啟動時加 --spring.profiles.active=cluster 才會用這份
# 不用六台都寫,連上其中一台就問得到整個 Cluster
spring:
  data:
    redis:
      cluster:
        nodes: 127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003 # 寫幾台當入口,一台掛了還有別台

Program arguments 改成 --spring.profiles.active=cluster 再啟動。

https://ithelp.ithome.com.tw/upload/images/20260930/201842098Bk0LvPo9y.png

Java 連上之後會問到每個 slot 在哪台,key 直接送去對的那台。

開另一個終端機,盯著 7001 收到的指令(MONITOR 會印出這台收到的每個指令,grep tag 只留 Day 7 標籤相關的,在主機的終端機下):

docker exec redis-7001 redis-cli -p 7001 MONITOR | grep tag

回到原本的終端機,用 Day 7 的標籤 API 打三筆,再查同時有 3C 跟 sale 的商品(在主機的終端機下):

curl -X POST "localhost:8080/tag/p1?tags=3C"

curl -X POST "localhost:8080/tag/p2?tags=3C,sale"

curl -X POST "localhost:8080/tag/p3?tags=sale"

curl "localhost:8080/tag/search?tags=3C,sale"   # ["p2"]

tag:3C 跟 tag:sale 不在同一個 slot,Java 卻沒噴 CROSSSLOT,答案 p2 也是對的。看一下 MONITOR:

https://ithelp.ithome.com.tw/upload/images/20260930/20184209XvcRXfMhXb.png

前兩行是打標籤的 SADD,第三行才是查詢。

Day 7 查詢用的是 SINTER,Redis 算好交集直接回答案。這次 7001 收到的卻是 SMEMBERS(全部撈出來)。

兩個 key 不在同一個 slot,Spring 就換個做法:

  1. 跟 7001 拿 tag:3C 底下全部的商品(p1、p2)
  2. 跟 7003 拿 tag:sale 底下全部的商品(p2、p3)
  3. 在 Java 裡自己比對,兩邊都有的是 p2

同一個查詢直接用 redis-cli 下,就是剛剛的 CROSSSLOT(在主機的終端機下):

docker exec redis-7001 redis-cli -c -p 7001 SINTER tag:3C tag:sale

https://ithelp.ithome.com.tw/upload/images/20260930/20184209DIaQLHrALc.png

為什麼要在意

  • Java 沒噴錯,不代表沒事。 Spring 拆開做,是把兩個標籤底下的商品整包搬回 Java 再算。一個標籤掛了幾十萬件商品,就是幾十萬筆資料走一趟網路。
  • 要一起動的 key,取名的時候就要想好。 單機時一個指令就做完的事,搬上 Cluster 可能噴 CROSSSLOT。上線之後才加 hash tag,key 的名字就變了,舊資料得全部搬一次。

明天

不管單機還是 Cluster,平常下指令都是一個一個送:送出去、等回覆、再送下一個。一次要寫一萬筆,就要來回一萬次。之前灌資料有用到的 --pipe 就是在解決這件事,明天繼續來看 Pipeline 跟 MULTI/images/emoticon/emoticon12.gif


上一篇
Day 17|Sentinel
下一篇
Day 19|Pipeline 與事務
系列文
從購物車到秒殺:30 天 Redis 高併發自學筆記 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言